//====================================================
// FSM for a write-back, write-allocate, no read-through (L1) cache		4/19/2012
//=====================================================
/*
	This is meant to be a finite state machine for of a cache controller for a write-back, write-allocate, no read-through, two way set associative cache.
...
*/
//Q's
// lower N order of cache bits,  tag size- we need to decide on cache structure?
// bidirectional vars
// registers,  wires, = and <=...I remember warnings about this from the presentation, and I am confused!
// l2_wr and l2_rd be pulled high for 1 cycle BEFORE loading the lines? more of an vhdl/verilog question- may have many timing errors
// uncacheable - largest 3 bits are 101 of cpu_addr. combinatorial forward to the L2
//ToDo: handle uncacheable reqs, use write mask properly, update localparams in meminterface, find the many design flaws
////timing on sel0...alternation of RR bit..
module fsm_L1 (
	clk, reset,
	cpu_addr, cpu_rd, cpu_wr, cpu_dout, cpu_wmask, cpu_din, cpu_dinok,																						//CPU bus
	l2_dout,  l2_dinok,  l2_dontcache, l2_addr, l2_rd, l2_wr, l2_din,	l2_wmask,																//L2 bus
	hitindex, dirtyindex, mem_din_1, mem_din_2, l1_out_rr, l1_out_valid,	l1_out_dirty,l1_out_index, mem_tagin_1, mem_tagin_2,				// Memory Interface to L1 control
	mem_addr, mem_dout, l1_in_tag, sel,	l1_in_rr, l1_in_valid, l1_in_dirty,l1_in_index 																	// L1 control to Memory Interface
);
//=============Internal Constants======================
localparam SIZE = 5;
localparam IDLE  = 5'b00001,COMPARETAG = 5'b00010,ALLOC = 5'b00100,WRITEBACK = 5'b01000;
localparam TAGMSB = 31;
localparam TAGLSB = 7;
localparam INDEXMSB = 6;
localparam INDEXLSB = 2;
localparam OFFSETMSB = 1;
localparam OFFSETLSB = 0;

//=============Input Ports=============================
//Global
input wire clk;
input wire reset;
//CPU to L1
input wire[31:0] cpu_addr;
input wire cpu_rd;
input wire cpu_wr;
input wire[31:0] cpu_dout;
input wire[3:0] cpu_wmask;
//L2 to L1 interface
input wire[31:0] l2_dout;
input wire l2_dinok;										//asserted if data is valid
input wire l2_dontcache;								//if set, do not cache this data
																//instead, return it exactly once
//Memory Interface to L1
input wire[1:0] hitindex;
input wire[1:0] dirtyindex;
input wire[31:0] mem_din_1;
input wire[31:0] mem_din_2;
input wire l1_out_rr;
input wire[1:0] l1_out_valid;
input wire[1:0] l1_out_dirty;
input wire[INDEXMSB:INDEXLSB] l1_out_index;
input wire[TAGMSB:TAGLSB] mem_tagin_1;
input wire[TAGMSB:TAGLSB] mem_tagin_2;

//=============Output Ports===========================
//L1 to CPU
output reg[31:0] cpu_din = 0;
output reg cpu_dinok = 0;
//L1 to L2
output reg[31:0] l2_addr = 0;
output reg l2_rd = 0;
output reg l2_wr = 0;
output reg[31:0] l2_din = 0;
output reg[3:0] l2_wmask = 0;
//L1 to Memory Interface
output reg[31:0] mem_addr = 0;
output reg[31:0] mem_dout = 0;
output reg[TAGMSB:TAGLSB] l1_in_tag = 0;
output reg[1:0] sel = 0;
output reg l1_in_rr = 0;
output reg l1_in_valid = 0;
output reg l1_in_dirty = 0;
output reg[INDEXMSB:INDEXLSB] l1_in_index = 0;

//=============Internal Variables======================
reg   [SIZE-1:0]          state        ;// Seq part of the FSM
reg   [SIZE-1:0]          next_state   ;// combo part of FSM

//==========Code==========================
always @ (posedge clk)
begin : FSM
if (reset == 1'b1) begin
  state <= #1 IDLE;
end else
 case(state)
   IDLE : if(cpu_rd | cpu_wr) begin
			   mem_addr <= cpu_addr;	//this might not be used
				l1_in_tag <= cpu_addr[TAGMSB:TAGLSB];
				l1_in_index <= cpu_addr[INDEXMSB:INDEXLSB];
				sel <= 2'b01;
				state <= #1 COMPARETAG;
			 end
   COMPARETAG : if (hitindex[0]==1) begin
					//Cache Hit - Read
						if(cpu_rd) begin
							//Pick the right set element to get the data from
							if(hitindex[1]==1) begin
								cpu_din <= mem_din_1;
							end else begin
								cpu_din <= mem_din_2;
							end
							cpu_dinok <= 1;
							state <= #1 IDLE;
						end else begin
						//Cache Hit - Write
							mem_dout <= cpu_dout;
							l1_in_tag <= cpu_addr[TAGMSB:TAGLSB];	
							l1_in_valid <= 1;
							l1_in_dirty <= 1;
							cpu_dinok <= 1;
							state <= #1 IDLE;
						end
					  end else begin
						//Miss - Check if the block is dirty
						if(dirtyindex[l1_in_rr]==1) begin
							//Dirty - Write its previous contents to the L2 cache
							if(l1_in_rr) begin
								l2_din <= mem_din_2;
								l2_addr <= {mem_tagin_2,l1_out_index};		
							end else begin
								l2_din <= mem_din_1;
								l2_addr <= {mem_tagin_1,l1_out_index};	
							end
							l2_wr <= 1;
							state <= #1 WRITEBACK;
						//Miss but clean - send L2 request for lower memory data immediately
						end else begin
							l2_addr <= cpu_addr;
							l2_rd <= 1;
							state <= #1 ALLOC;
						end
					end

   ALLOC : if (l2_dinok == 0) begin
	            state <= #1 ALLOC;
           end else begin
					if(cpu_rd) begin
						//Read Miss - mark the block as clean
						l1_in_dirty <= 0;
					end else begin
						//Write Miss - mark the block as dirty
						l1_in_dirty <= 1;
					end
					//Read data from lower memory into cache block
					l1_in_valid <= 1;
					l1_in_index <= cpu_addr[INDEXMSB:INDEXLSB];
					//The next locating of a block will be alternated (Round Robin)
					l1_in_rr <= ~l1_out_rr;		//these variables are redundant
					l1_in_index <= l1_out_rr;
					//Read data to cache block
					sel <= 2'b10;
					state <= #1 ALLOC;					
					//If Read - return the data
					if(cpu_rd) begin
						cpu_din <= l2_dout;
					end
					cpu_dinok <= 1;
					state <= #1 IDLE;
			  end
   WRITEBACK : 
				if (l2_dinok == 0) begin
						state <= #1 WRITEBACK;
              end else begin
						l2_addr <= cpu_addr;
						l2_rd <= 1;
						state <= #1 ALLOC;	
				  end
 
   default : state <= #1 IDLE;
endcase
end

endmodule // End of Module arbiter
